
package org.scalabletype.ws;

import java.beans.Beans;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.sql.SQLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBException;

import com.xerox.amazonws.common.JAXBuddy;

import org.scalabletype.data.DataHelper;
import org.scalabletype.ws.jaxb.Atlas;
import org.scalabletype.ws.jaxb.RestApi;
import org.scalabletype.ws.jaxb.RestApi.Service;
import org.scalabletype.ws.jaxb.ObjectFactory;


/**
 * This class handles the blog create web service call
 * params:
 *  - title
 *  - slogan
 *
 * @author D. Kavanagh
 */
public class RestAPIServlet extends javax.servlet.http.HttpServlet {
	RestApi api;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
		String configFileName = config.getInitParameter("config");
		try {
			InputStream iStr = this.getClass().getClassLoader().getResourceAsStream(configFileName);
			api = (RestApi)JAXBuddy.deserializeXMLStream(RestApi.class, iStr);
		} catch (JAXBException jex) {
			jex.printStackTrace();
		}
    }

	// simple wildcard at the end
	private boolean matches(String s1, String pattern) {
	System.out.println("pattern:"+pattern+"  matching:"+s1);
		if (pattern.endsWith("*")) {
			return s1.startsWith(pattern.substring(0, pattern.length()-1));
		}
		else {
			return s1.equals(pattern);
		}
	}

	private Service getService(String queryString) {
		Service ret = null;
		int depth = 0;	// 
		for (Service s : api.getServices()) {
			String sUrl = s.getUrl();
			if (matches(queryString, sUrl)) {
				// calculate url depth based on count of '/'s in pattern
				int tmp = sUrl.split("/").length;
				if (ret == null) {
					depth = tmp;
					ret = s;
				}
				else if (depth > tmp) {
					depth = tmp;
					ret = s;
				}
			}
		}
		if (ret == null) {
			System.out.println("Couldn't match :"+queryString);
		}
		return ret;
	}

	private void enumerate(ObjectFactory of, Atlas.Api api, Class clazz) {
		try {
			BeanInfo bi = Introspector.getBeanInfo(clazz);
			PropertyDescriptor [] properties = bi.getPropertyDescriptors();
			for (PropertyDescriptor pd : properties) {
				String name = pd.getName();
				if (!name.startsWith("callback") && !name.equals("class")) {
					Atlas.Api.Property prop = of.createAtlasApiProperty();
					prop.setValue(name);
					Class propClass = pd.getPropertyType();
					String type = propClass.getName();
					type = type.substring(type.lastIndexOf('.')+1);
					if (propClass.isPrimitive() || type.equals("String")) {
						type = type.toLowerCase();
					}
					prop.setType(type);
					String retType = pd.getReadMethod().getGenericReturnType().toString();
					if (retType.endsWith(">")) { // get the contained class
						String ofType = retType.substring(retType.lastIndexOf('.')+1, retType.length()-1);
						prop.setOfType(ofType);
					}
					api.getProperties().add(prop);
				}
			}
		} catch (IntrospectionException ex) {
			ex.printStackTrace();
		}
	}

	private void sendAtlas(HttpServletRequest request, OutputStream oStr, boolean xml) throws IOException, JAXBException {
		if (xml) {
			ObjectFactory of = new ObjectFactory();
			Atlas atlas = of.createAtlas();
			atlas.setVersion("0.1");
			atlas.setUser(request.getRemoteUser());
			for (Service s : api.getServices()) {
				try {
					Atlas.Api api = of.createAtlasApi();
					String objName = s.getEntity();
					api.setName(objName.substring(objName.lastIndexOf('.')+1));
					String url = s.getUrl();
					System.err.println("Service : "+url);
					if (url.endsWith("*")) {
						url = url.substring(0, url.length()-1);
					}
					api.setHref(url);
					String name = s.getEntity();
					Class clazz = Class.forName(name);
					enumerate(of, api, clazz);
					atlas.getApis().add(api);
				} catch (ClassNotFoundException ex) {
					ex.printStackTrace();
				}
			}
			InputStream iStr = JAXBuddy.serializeXMLFile(Atlas.class, atlas);
			Copier.copyStreams(iStr, oStr);
		} else {	// emit JSON
			oStr.write(("{\'items\': [").getBytes());
			boolean first = true;
			for (Service s : api.getServices()) {
				if (!first) {
					if (!xml) {
						oStr.write(",\n".getBytes());
					}
				}
				first = false;
				String objName = s.getEntity();
				oStr.write("{\'api\':\'".getBytes());
				oStr.write(objName.substring(objName.lastIndexOf('.')+1).getBytes());
				oStr.write("\',\n\'href\':\'".getBytes());
				oStr.write(s.getUrl().getBytes());
				oStr.write("\'}".getBytes());
			}
			oStr.write("],".getBytes());
			oStr.write("\'user\':\'".getBytes());
			oStr.write(request.getRemoteUser().getBytes());
			oStr.write("\',\n\'version\':\'0.1\'}".getBytes());
		}
	}

    /**
     * This method returns 
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

		System.err.println("handling get");
		try {
			boolean xml = request.getHeader("Accept").equals("text/xml");
			String urlPattern = request.getPathInfo();
			System.err.println("pattern = "+urlPattern);
			if (xml) {
				response.setContentType("text/xml");
			}
			else {
				response.setContentType("application/json");
			}
			if (urlPattern == null) {
				// serve up atlas
				sendAtlas(request, response.getOutputStream(), xml);
			}
			else {	// server up entities
				Service service = getService(urlPattern.substring(1));
				String name = service.getEntity();
				EntityManager em = DataHelper.getEntityManager();
				StringBuilder queryString = new StringBuilder("select o from ");
				queryString.append(name);
				queryString.append(" o");
				// formulate where clause, if any
				Enumeration keys = request.getParameterNames();
				boolean first = true;
				while (keys.hasMoreElements()) {
					String key = (String)keys.nextElement();
					String value = (String)request.getParameterValues(key)[0];
					if (first) {
						queryString.append(" where");
						first = false;
					}
					else {
						queryString.append(" and");
					}
					queryString.append(" ");
					queryString.append(key);
					queryString.append(" = '");
					queryString.append(value);
					queryString.append("'");
				}
				Query query = em.createQuery(queryString.toString());
				OutputStream oStr = response.getOutputStream();
				List objs = query.getResultList();
				if (xml) {
					oStr.write(("<Items>").getBytes());
				}
				else {
					oStr.write(("{\'items\': [").getBytes());
				}
				first = true;
				for (Object obj : objs) {
					if (!first) {
						if (!xml) {
							oStr.write(",\n".getBytes());
						}
					}
					first = false;
					serialize(oStr, obj, xml);
				}
				if (xml) {
					oStr.write(("</Items>").getBytes());
				}
				else {
					oStr.write("]}".getBytes());
				}
				response.flushBuffer();
			}
		} catch (Throwable t) {
			// pretty serious error
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "unexpected error : "+t.getMessage());
			t.printStackTrace();
		} finally {
        	response.flushBuffer();
		}
    }

	private void serialize(OutputStream oStr, Object obj, boolean xml) {
		try {
			BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
			PropertyDescriptor [] properties = bi.getPropertyDescriptors();
			String objName = obj.getClass().getSimpleName();
			objName = objName.substring(0, objName.indexOf("$$"));
			if (xml) {
				oStr.write(("<"+objName+">").getBytes());
			}
			else {
				oStr.write(("{").getBytes());
			}
			boolean first = true;
			for (PropertyDescriptor pd : properties) {
				String name = pd.getName();
				if (!name.startsWith("callback") && !name.equals("class")) {
					if (!first) {
						if (!xml) {
							oStr.write(",\n".getBytes());
						}
					}
					first = false;
					if (xml) {
						oStr.write(("<"+name+">").getBytes());
					}
					else {
						oStr.write(("\'"+name+"\': \'").getBytes());
					}
					// get value
					Method m = pd.getReadMethod();
					Object val = m.invoke(obj, new Object [] {});
					if (val != null) {
						oStr.write(val.toString().getBytes());
					}
					if (xml) {
						oStr.write(("</"+name+">").getBytes());
					}
					else {
						oStr.write("\'".getBytes());
					}
				}
			}
			if (xml) {
				oStr.write(("</"+objName+">").getBytes());
			}
			else {
				oStr.write("\n}".getBytes());
			}
		} catch (IllegalAccessException ex) {
			ex.printStackTrace();
		} catch (InvocationTargetException ex) {
			ex.printStackTrace();
		} catch (IntrospectionException ex) {
			ex.printStackTrace();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	// for doing updates of resources, and/or create for other entities
    public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
		Service service = getService(request.getPathInfo().substring(1));
		String name = service.getEntity();
		System.err.println("using Entity: "+name);
		String resource = request.getPathInfo().substring(service.getUrl().length());
		System.err.println("Going to create new resourced named : "+resource);
		try {
			Object obj = Beans.instantiate(getClass().getClassLoader(), name);
			BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
			PropertyDescriptor [] properties = bi.getPropertyDescriptors();
			for (PropertyDescriptor pd : properties) {
				String propName = pd.getName();
				if (!propName.startsWith("callback") && !propName.equals("class")) {
					Method m = pd.getWriteMethod();
					if (propName.equals("name")) {
						m.invoke(obj, new Object [] {resource});
					}
					else {
						System.out.println("Looking for param : "+propName);
						String val = request.getParameter(propName);
						System.out.println("Found param value : "+val);
						if (val != null && !val.trim().equals("")) {
							m.invoke(obj, new Object [] {val});
						}
					}
				}
			}
			EntityManager em = DataHelper.getEntityManager();
			em.persist(obj);
			response.setStatus(HttpServletResponse.SC_OK);
			response.flushBuffer();
		} catch (Throwable t) {
			// pretty serious error
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "unexpected error : "+t.getMessage());
			t.printStackTrace();
		} finally {
        	response.flushBuffer();
		}
	}

    // resource creation
    public void doPut(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
		Service service = getService(request.getPathInfo().substring(1));
		String name = service.getEntity();
		System.err.println("using Entity: "+name);
		String resource = request.getPathInfo().substring(service.getUrl().length());
		System.err.println("Going to create new resource named : "+resource);
		try {
			Object obj = Beans.instantiate(getClass().getClassLoader(), name);
			BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
			PropertyDescriptor [] properties = bi.getPropertyDescriptors();
			for (PropertyDescriptor pd : properties) {
				String propName = pd.getName();
				if (!propName.startsWith("callback") && !propName.equals("class")) {
					Method m = pd.getWriteMethod();
					if (propName.equals("name")) {
						m.invoke(obj, new Object [] {resource});
					}
					else {
						System.out.println("Looking for param : "+propName);
						String val = request.getParameter(propName);
						System.out.println("Found param value : "+val);
						if (val != null && !val.trim().equals("")) {
							m.invoke(obj, new Object [] {val});
						}
					}
				}
			}
			EntityManager em = DataHelper.getEntityManager();
			em.persist(obj);
			response.setStatus(HttpServletResponse.SC_OK);
			response.flushBuffer();
		} catch (Throwable t) {
			// pretty serious error
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "unexpected error : "+t.getMessage());
			t.printStackTrace();
		} finally {
        	response.flushBuffer();
		}
    }


    public void doDelete(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
		Service service = getService(request.getPathInfo().substring(1));
		String name = service.getEntity();
		System.err.println("deleting an Entity: "+name);
		String resource = request.getPathInfo().substring(service.getUrl().length());
		System.err.println("Going to delete resource named : "+resource);
		try {
			EntityManager em = DataHelper.getEntityManager();
			Class clazz = Class.forName(name);
			Object obj = em.find(clazz, resource);
			em.remove(obj);
			response.setStatus(HttpServletResponse.SC_OK);
			response.flushBuffer();
		} catch (Throwable t) {
			// pretty serious error
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "unexpected error : "+t.getMessage());
			t.printStackTrace();
		} finally {
        	response.flushBuffer();
		}
	}
}
